/*******************************************************************************
 * Copyright 2010 Ben Knowles
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.midtro.attolog;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Date;

/**
 * Logs messages to the console or a file, depending on the configuration.
 * 
 * Adapted from the 'SimpleLogger' class.
 * 
 * @author Ben Knowles
 */
public class AttoLogger {

	private final LoggerManager manager;
	private final String displayName;
	private final String configName;
	private Level level = Level.INFO;
	
	/**
	 * Creates a new logger.
	 * Uses the class' short name.
	 * @param sourceClass The class to log.
	 */
	public AttoLogger(Class<?> sourceClass){
		this(sourceClass, false);
	}
		
	/**
	 * Creates a new logger with name customization.
	 * @param sourceClass The class to log.
	 * @param useLongName Whether to use the full name including the package, or just the class name.
	 */
	public AttoLogger(Class<?> sourceClass, boolean useLongName){
		this.manager = LoggerManager.getInstance();
		this.configName = sourceClass.getName().replace('$', '.');
		this.displayName = useLongName ? this.configName : sourceClass.getSimpleName();
		this.manager.configure(this);
	}
	
	/**
	 * Creates the object array of parameters to use in the formatter.
	 * @param level The level.
	 * @param extraArguments The number of blank indicies to create.
	 * @return The data array.
	 */
	private Object[] createData(Level level, int extraArguments){
		Object[] result = new Object[4 + extraArguments];
		result[0] = new Date();
		result[1] = Thread.currentThread().getName();
		result[2] = displayName;
		result[3] = level.getName().toUpperCase();
		return result;
	}
	
	/**
	 * Logs an object.
	 * This method will check if the object is an object array or primitive array
	 * and use the {@link java.util.Arrays} methods to convert that array to a string.
	 * 
	 * This is an improvement over SimpleLog, which used a half assed implementation.
	 * 
	 * @param level The level.
	 * @param objectName The object name.
	 * @param value The object.
	 */
	private void log(Level level, String objectName, Object value){
		if(!this.level.shouldLog(level)){
			return;
		}
		
		final Object obj;
		
		// Format the array into a comma separated string
		if(value.getClass().isArray()){
			final StringBuilder sb = new StringBuilder("[");
			
			// Append each array element, comma separated
			for(int i = 0; i < Array.getLength(value); i++){
				sb.append(String.valueOf(Array.get(value, i)));
				sb.append(", ");
			}
			
			// Delete last ', '
			if(Array.getLength(value) > 0){
				sb.delete(sb.length()-2, sb.length());
			}

            sb.append("]");
            
            obj = sb.toString();
		} else {
			// Not an array, so just pass the value as it is
			obj = value;
		}
		
		final Object[] data = createData(level, 2);
		data[data.length - 2] = objectName;
		data[data.length - 1] = obj;
		manager.println(manager.getObjectFormat().format(data), this);
	}
	
	/**
	 * Logs a message.
	 * @param level The level.
	 * @param message The message.
	 */
	private void log(Level level, String message){
		if(!this.level.shouldLog(level)){
			return;
		}
		
		final Object[] data = createData(level, 1);
		data[data.length - 1] = message;
		manager.println(manager.getMessageFormat().format(data), this);
	}
	
	/**
	 * Logs an exception.
	 * @param level The level.
	 * @param throwable The exception.
	 */
	private void log(Level level, Throwable throwable){
		if(!this.level.shouldLog(level)){
			return;
		}
		
		final Object[] data = createData(level, 1);
		data[data.length - 1] = throwable;
		manager.println(manager.getExceptionFormat().format(data), this);
	}
	
	/**
	 * Logs a fatal message.
	 * @param message The message.
	 */
	public void fatal(String message){
		log(Level.FATAL, message);
	}
	
	/**
	 * Logs an error message.
	 * @param message The message.
	 */
	public void error(String message){
		log(Level.ERROR, message);
	}
	
	/**
	 * Logs a warning message.
	 * @param message The message.
	 */
	public void warn(String message){
		log(Level.WARN, message);
	}
	
	/**
	 * Logs an info message.
	 * @param message The message.
	 */
	public void info(String message){
		log(Level.INFO, message);
	}
	
	/**
	 * Logs a debug message.
	 * @param message The message.
	 */
	public void debug(String message){
		log(Level.DEBUG, message);
	}
	
	/**
	 * Logs a verbose message.
	 * @param message The message.
	 */
	public void verbose(String message){
		log(Level.VERBOSE, message);
	}
	
	/**
	 * Logs a ludicrous message.
	 * @param message The message.
	 */
	public void ludicrous(String message){
		log(Level.LUDICROUS, message);
	}
	
	public void infoObject(String objectName, Object val){
		log(Level.INFO, objectName, val);
	}
		
	public void infoObject(String objectName, Object[] val){
		log(Level.INFO, objectName, Arrays.toString(val));
	}
	
	public void infoObject(String objectName, boolean val){
		log(Level.INFO, objectName, String.valueOf(val));
	}
		
	public void infoObject(String objectName, boolean[] val){
		log(Level.INFO, objectName, Arrays.toString(val));
	}
	
	public void infoObject(String objectName, int val){
		log(Level.INFO, objectName, String.valueOf(val));
	}
		
	public void infoObject(String objectName, int[] val){
		log(Level.INFO, objectName, Arrays.toString(val));
	}
	
	public void debugObject(String objectName, Object val){
		log(Level.DEBUG, objectName, val);
	}
	
	public void debugObject(String objectName, boolean val){
		log(Level.DEBUG, objectName, String.valueOf(val));
	}
	
	public void debugObject(String objectName, int val){
		log(Level.DEBUG, objectName, String.valueOf(val));
	}
	
	public void verboseObject(String objectName, Object val){
		log(Level.VERBOSE, objectName, val);
	}
	
	public void verboseObject(String objectName, boolean val){
		log(Level.VERBOSE, objectName, String.valueOf(val));
	}
	
	public void verboseObject(String objectName, int val){
		log(Level.VERBOSE, objectName, String.valueOf(val));
	}
	
	public void ludicrousObject(String objectName, Object val){
		log(Level.LUDICROUS, objectName, val);
	}
	
	public void ludicrousObject(String objectName, boolean val){
		log(Level.LUDICROUS, objectName, String.valueOf(val));
	}
	
	public void ludicrousObject(String objectName, int val){
		log(Level.LUDICROUS, objectName, String.valueOf(val));
	}
	
	/**
	 * Logs a fatal exception.
	 * @param t The exception.
	 */
	public void fatalException(Throwable t){
		log(Level.FATAL, t);
	}
	
	/**
	 * Logs an error exception.
	 * @param t The exception.
	 */
	public void errorException(Throwable t){
		log(Level.ERROR, t);
	}
	
	/**
	 * Logs a warning exception.
	 * @param t The exception.
	 */
	public void warnException(Throwable t){
		log(Level.WARN, t);
	}
	
	/**
	 * Gets the config name.
	 */
	String getConfigName(){
		return configName;
	}
	
	/**
	 * Sets the level of this logger.
	 * @param level The level to set.
	 */
	void setLevel(Level level){
		this.level = level;
	}
}
